﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace FStore.Utils
{
    /// <summary>
    /// Class for utility's that are internal to the library
    /// </summary>
    public static class InternalUtils
    {
        /// <summary>
        /// Extension method to help with string formatting
        /// </summary>
        /// <param name="str">The format string to populate</param>
        /// <param name="args">The arguments needed for the string</param>
        /// <returns>The formatted string</returns>
        public static string UFormat(this string str, params object[] args)
        {
            return string.Format(str, args);
        }

        /// <summary>
        /// Extension method to retrieve a Unicode byte array of a string
        /// </summary>
        /// <param name="str">The string to get the array from</param>
        /// <returns>The byte array</returns>
        public static byte[] UAsByteArray(this string str)
        {
            return UAsByteArray(str, Encoding.Unicode);
        }

        /// <summary>
        /// Extension method to convert a string to a byte array of a given encoding
        /// </summary>
        /// <param name="str">The string to generate the byte array from</param>
        /// <param name="enc">The encoding to use</param>
        /// <returns></returns>
        public static byte[] UAsByteArray(this string str, Encoding enc)
        {
            return enc.GetBytes(str);
        }
    }

    /// <summary>
    /// Class containing utilities used internally and available for external use
    /// </summary>
    public static class PublicUtils
    {
        /// <summary>
        /// Calculates the HEX values of an array of bytes and produces a hex string
        /// </summary>
        /// <param name="data">The byte array to calculate the Hex values for</param>
        /// <param name="hyphenate">Whether to hyphenate the Hex values</param>
        /// <returns></returns>
        public static string UAsHex(this byte[] data, bool hyphenate = false)
        {
            StringBuilder sb = new StringBuilder();

            foreach (byte by in data)
            {
                sb.Append(by.ToString("X2"));
                if (hyphenate)
                    sb.Append("-");
            }

            return sb.ToString().TrimEnd('-');
        }

        /// <summary>
        /// Takes a string and turns it into a base-64 Unicode string
        /// </summary>
        /// <param name="data">The string to convert to base-64</param>
        /// <returns>The base-64 string</returns>
        public static string UAsBase64(this string data)
        {
            return UAsBase64(data, Encoding.Unicode);
        }

        /// <summary>
        /// Takes a string and turns it into a base-64 string of a given encoding
        /// </summary>
        /// <param name="data">The string to convert to base-64</param>
        /// <param name="enc">The encoding to use</param>
        /// <returns>The base-64 string</returns>
        public static string UAsBase64(this string data, Encoding enc)
        {
            return Convert.ToBase64String(enc.GetBytes(data));
        }

        /// <summary>
        /// Gets the rightmost n chars from a string
        /// </summary>
        /// <param name="data">The string to get the chars from</param>
        /// <param name="count">The number of chars to get</param>
        /// <returns>The rightmost n chars</returns>
        public static string URight(this string data, int count)
        {
            return data.Substring(data.Length - count);
        }

        /// <summary>
        /// Takes a dictionary and reverses the values and keys
        /// </summary>
        /// <typeparam name="TKey">The key type</typeparam>
        /// <typeparam name="TValue">The value type</typeparam>
        /// <param name="dict">The dictionary to flip</param>
        /// <returns>A flipped version of the dictionary</returns>
        public static Dictionary<TValue, TKey> FlipValueToKey<TKey, TValue>(this Dictionary<TKey, TValue> dict)
        {
            Dictionary<TValue, TKey> nDict = new Dictionary<TValue, TKey>();

            foreach (var item in dict)
            {
                nDict.Add(item.Value, item.Key);
            }

            return nDict;
        }
    }

    public sealed class NotifyPropHelper
    {
        private Dictionary<string, object> _props = new Dictionary<string, object>();
        private object _caller = null;

        public NotifyPropHelper(object caller)
        {
            if (caller == null)
            {
                throw new ArgumentNullException("Caller cannot be null, recommend declaring type");
            }

            _caller = caller;
        }

        public bool AddProperty(string name)
        {
            try
            {
                _props.Add(name, null);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public void GetNotifiableProperties()
        {
            foreach (PropertyInfo pi in _caller.GetType().GetProperties().Where(p => p.IsDefined(
                typeof(NotifiableProperty), true)))
            {
                _props.Add(pi.Name, null);
            }
        }

        public T GetValue<T>(string name)
        {
            return (T)_props[name];
        }

        public T GetValue<T>()
        {
            StackTrace st = new StackTrace();
            StackFrame sf = st.GetFrame(1);
            MethodBase mi = sf.GetMethod();
            if (mi.Name.StartsWith("_get") == false)
            {
                string propName = mi.Name.Substring("_get".Length, mi.Name.Length - "_get".Length);

                return (T)_props[propName];
            }
            else
            {
                throw new Exception("NotifiableGetValue requires to have been immediately called from a get accessor");
            }
        }

        public void SetValue<T>(T value, PropertyChangedEventHandler e)
        {
            StackTrace st = new StackTrace();
            StackFrame sf = st.GetFrame(1);
            MethodBase mi = sf.GetMethod();
            if (mi.Name.StartsWith("_set") == false)
            {
                string propName = mi.Name.Substring("_set".Length, mi.Name.Length - "_set".Length);

                _props[propName] = value;

                if (e != null)
                {
                    e(_caller, new PropertyChangedEventArgs(propName));
                }
            }
            else
            {
                throw new Exception("NotifiableGetValue requires to have been immediately called from a set accessor");
            }
        }
    }

    /// <summary>
    /// Marks a property as notifiable for automatic referencing by <c>FStor.Utils.NotifyPropHelper</c>
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class NotifiableProperty : Attribute { }

}